#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<errno.h>
#include<sys/stat.h>
#include<fcntl.h>

#define SIZE 100
#define COMMAND_SIZE 200
#define ZEOR '\0'
#define ARGV_NUM 32
#define SKIP " "
#define SkipPath(p) do{p+=strlen(p)-1; while(*p!='/'){p--;}}while(0)
#define SkipSpace(command,pos) do{\
  while(command[pos]==' ')pos++;\
}while(0)

#define None_Redir 0
#define In_Redir 1
#define Out_Redir 2
#define App_Redir 3

char* gArgv[ARGV_NUM];//指令参数
char cwd[SIZE*2];//当前路径的环境变量键值对
int lastcode=0;
char* filename=NULL;//重定向文件名
int Redir_Type=0;

const char* GetUserName(){//获取环境变量中的值
  const char* name=getenv("USER");
  if(name==NULL){
    return "None";
  }
  return name;
}

const char* GetHome(){//获取家目录
  const char* home=getenv("HOME");
  if(home==NULL){
    return "/";
  }
  return home;
}

const char* GetHostHome(){//获取主机名
  const char* hostname=getenv("HOSTNAME");
  if(hostname==NULL){
    return "None";
  }
  return hostname;
}

const char* GetCwd(){//获取当前路径
  const char* cwd=getenv("PWD");
  if(cwd==NULL){
    return "None";
  }
  return cwd;
}


//输出命令行
void MakeCommandLineAndPrint(){
    char line[SIZE];
    const char* name=GetUserName();
    const  char* hostname=GetHostHome();
    const char* cwd=GetCwd();
    SkipPath(cwd);
    snprintf(line,sizeof(line),"[%s@%s %s]> ",name,hostname,cwd=strlen(cwd)==1?"/":cwd+1);
    printf("%s",line);
    fflush(stdout);
}
//获取用户命令行
int GetUserCommand(char command[],size_t n){
  char* s=fgets(command,n,stdin);
  if(s==NULL)return -1;
  command[strlen(command)-1]=ZEOR;
  return (int)strlen(command);
}


//命令行分割,获取命令行参数列表
void SplitCommand(char command[],size_t n){
   gArgv[0]=strtok(command,SKIP);
   size_t index=1;
   char* t=gArgv[0];
   while(t!=NULL){
     t=strtok(NULL,SKIP);
      gArgv[index++]=t;
   }
   //size_t i=0;
   //for(;i<index;i++){
   //  printf("%s\n",gArgv[i]);
   //}
}

//cd内建命令
void Cd(){
  const char* path=gArgv[1];
  if(path==NULL){
    path=GetHome();
  }
  //更新当前工作目录
  chdir(path);//修改当前进程的工作路径

  //更新环境变量
  char t[SIZE*2];
  getcwd(t,sizeof(t));
  snprintf(cwd,sizeof(cwd),"PWD=%s",t);
  putenv(cwd);
}

//查看是否是内建命令
int CheckBuiltIn(){
  int yes=0;
  const char* cmd=gArgv[0];
  if(strcmp(cmd,"cd")==0){
    yes=1;
    Cd();
  }
  return yes;
}
// 处理创建子进程失败
void Die(){
  exit(1);
}

//区别重定向类型
void CheckRedir(char* command){
   int pos=0;
   int end=strlen(command);

   while(pos<end){
     if(command[pos]=='>'){
       if(command[pos+1]=='>'){
         Redir_Type=App_Redir;     
         command[pos++]='\0';
         pos++;
         SkipSpace(command,pos);
         filename=command+pos;
       }else{
         Redir_Type=Out_Redir;
         command[pos++]='\0';
         SkipSpace(command,pos);
         filename=command+pos;

       }
     }else if(command[pos]=='<'){
         Redir_Type=In_Redir;
         command[pos++]='\0';
         SkipSpace(command,pos);
         filename=command+pos;
     }else{
       pos++;
     }
   }
}



//执行指令
void ExeCommand(){ 
   pid_t id=fork();
   if(id<0){
     Die();
   }
   if(id==0){
     //child
     if(filename!=NULL){
       if(Redir_Type==In_Redir){
        int fd= open(filename,O_RDONLY,0666);
        dup2(fd,0);
       }else if(Redir_Type==Out_Redir){
         int fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
         dup2(fd,1);
       }else if(Redir_Type==App_Redir){
         int fd=open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);
         dup2(fd,1);
       }else{}

     }
     execvp(gArgv[0],gArgv);
     exit(errno);
   }else{
     int status=0;
     pid_t res=waitpid(id,&status,0);
     if(res>0){
       lastcode=WEXITSTATUS(status);//获取子进程退出码信息
       if(WIFEXITED(status)){
        if(lastcode!=0) printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);
       }
     }
   }
}

int main(){
  int quit=0;
  while(!quit){
     //1.输出一个命令行
     MakeCommandLineAndPrint();
    
     //2.获取用户命令
     char command[COMMAND_SIZE];
     int n= GetUserCommand(command,sizeof(command));
     if(n<=0)return 1;//输入指令不合法
    // printf("%s\n",command);
    //2.1检查是否有重定向符，并分割
     CheckRedir(command);
    //3.命令行字符串分割
     SplitCommand(command,sizeof(command));
    
     //4.查看命令是否是内建命令
     int flag=CheckBuiltIn();
     if(flag)continue;
     
     //5.执行指令
     ExeCommand();
  }
  return 0;
}

